package cn.bby.query.server.infrastructure.db.repository;

import cn.bby.jtk.base.constant.CommonErrorCode;
import cn.bby.jtk.base.exception.ServiceException;
import cn.bby.jtk.bean.util.FastBeanUtil;
import cn.bby.query.server.domain.agg.metadata.entity.DsSettingsInfo;
import cn.bby.query.server.domain.agg.metadata.entity.DsTableInfo;
import cn.bby.query.server.domain.agg.metadata.repository.DsSettingsRepository;
import cn.bby.query.server.infrastructure.db.entity.DsSettingsDb;
import cn.bby.query.server.infrastructure.db.entity.DsSettingsTable;
import cn.bby.query.server.infrastructure.db.mapper.DsSettingsDbMapper;
import cn.bby.query.server.infrastructure.db.mapper.DsSettingsTableMapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.LocalCachedMapOptions;
import org.redisson.api.RLocalCachedMap;
import org.redisson.api.RedissonClient;
import org.redisson.api.map.MapLoader;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author ben
 * @since 2025/4/26 15:49
 */
@Slf4j
@Component
public class DsSettingsRepositoryImpl implements DsSettingsRepository {
    private final RLocalCachedMap<String, DsTableInfo> tableCache;
    @Resource
    private DsSettingsDbMapper dsSettingsDbMapper;
    @Resource
    private DsSettingsTableMapper dsSettingsTableMapper;

    public DsSettingsRepositoryImpl(RedissonClient redissonClient) {
        LocalCachedMapOptions<String, DsTableInfo> options = LocalCachedMapOptions.<String, DsTableInfo>defaults()
                .cacheSize(1000)
                .storeCacheMiss(true)
                .syncStrategy(LocalCachedMapOptions.SyncStrategy.UPDATE)
                .reconnectionStrategy(LocalCachedMapOptions.ReconnectionStrategy.LOAD)
                .loader(new MapLoader<>() {
                    @Override
                    public DsTableInfo load(String fullName) {
                        int idx = fullName.indexOf(".");
                        if (idx < 1) {
                            return null;
                        }
                        DsSettingsTable table = dsSettingsTableMapper.getTableInfoByFullName(fullName.substring(0, idx), fullName.substring(idx + 1));
                        if (table == null) {
                            return null;
                        }
                        return FastBeanUtil.copyProperties(table, new DsTableInfo());
                    }

                    @Override
                    public Iterable<String> loadAllKeys() {
                        return dsSettingsTableMapper.selectList(Wrappers.lambdaQuery(DsSettingsTable.class)
                                        .select(DsSettingsTable::getTableName)
                                        .groupBy(DsSettingsTable::getTableName))
                                .stream()
                                .map(DsSettingsTable::getTableName)
                                .toList();
                    }
                });

        tableCache = redissonClient.getLocalCachedMap("ds_table_info_map", options);
    }

    @Override
    public Map<String, Long> getAllTableNameIdMap() {
        return getAllTableInfos()
                .stream()
                .collect(Collectors.toMap(DsSettingsInfo::getTableName, DsSettingsInfo::getTableId));
    }

    @Override
    public List<DsSettingsInfo> getAllTableInfos() {
        return dsSettingsDbMapper.queryAllTableInfos();
    }

    @Override
    public Collection<DsTableInfo> getTableInfoByNames(Set<String> fullNames) {
        return tableCache.getAll(fullNames).values();
    }

    @Transactional
    @Override
    public void createTableInfos(Set<String> tableFullNames) {
        Map<String, Set<String>> dbTables = tableFullNames.stream()
                .map(name -> {
                    int i = name.indexOf('.');
                    if (i < 0) {
                        throw new ServiceException(CommonErrorCode.PARAM_ERROR, "表名格式错误")
                                .setData(name);
                    }
                    return Map.entry(name.substring(0, i), name.substring(i + 1));
                }).collect(Collectors.groupingBy(
                                Map.Entry::getKey,
                                Collectors.mapping(Map.Entry::getValue, Collectors.toSet())
                        )
                );

        // 数据库
        Map<String, Long> dbNameIdMap = getAllTableInfos()
                .stream()
                .collect(Collectors.toMap(DsSettingsInfo::getDbName, DsSettingsInfo::getDbId));
        Set<String> notExistDb = dbTables.keySet()
                .stream().filter(dbName -> dbNameIdMap.get(dbName) == null)
                .collect(Collectors.toSet());
        if (!notExistDb.isEmpty()) {
            log.warn("数据库不存在，创建后请维护：{}", notExistDb);
            createDb(notExistDb);
        }
        Map<String, Long> finalDbNameIdMap = notExistDb.isEmpty() ? dbNameIdMap : getAllTableInfos()
                .stream()
                .collect(Collectors.toMap(DsSettingsInfo::getDbName, DsSettingsInfo::getDbId));
        List<DsSettingsTable> tables = dbTables.entrySet().stream()
                .flatMap(entry -> {
                    Long dbId = finalDbNameIdMap.get(entry.getKey());
                    return entry.getValue().stream()
                            .map(tableName -> {
                                DsSettingsTable info = new DsSettingsTable();
                                info.setDbId(dbId);
                                info.setTableName(tableName);
                                return info;
                            });
                }).toList();
        // 保存
        dsSettingsTableMapper.insert(tables);
        tableCache.putAll(tables.stream()
                .map(info -> FastBeanUtil.copyProperties(info, new DsTableInfo()))
                .collect(Collectors.toMap(DsTableInfo::getTableName, info -> info))
        );
    }

    private void createDb(Set<String> dbNames) {
        List<DsSettingsDb> dbs = dbNames.stream()
                .map(dbName -> {
                    DsSettingsDb dsSettingsDb = new DsSettingsDb();
                    dsSettingsDb.setDbName(dbName);
                    return dsSettingsDb;
                })
                .toList();
        dsSettingsDbMapper.insert(dbs);
    }
}
